package veetouch.network;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.parsers.DocumentBuilderFactory;

import veetouch.syncpc.VtAndroidSyncActivity;

import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import android.widget.Toast;


public class ConnectionManager extends Thread
{
	// Msg tag
	private static final String MSGTYPE_PHOTO 	= "photo";
	private static final String MSGTYPE_LINK 	= "link";
	private static final String MSGTYPE_CONTACT = "contact";
	private static final String MSGTYPE_END 	= "end";
	
	// Msg pattern
	private static Pattern MSG_PHOTO_PATTERN   = Pattern.compile("^<photo .*?/>");
	private static Pattern MSG_CONTACT_PATTERN = Pattern.compile("^<contact .*?/>");
	private static Pattern MSG_END_PATTERN     = Pattern.compile("^<end .*?/>");
	
	private VtAndroidSyncActivity syncAct;
	
	private Socket socket;
	
	private InputStream in;
	private OutputStream out;
	
	public boolean runProcess = false; // Change to false when want to destroy thread
	
	public ConnectionManager(VtAndroidSyncActivity syncAct)
	{
		this.syncAct = syncAct;
	}
	
	private static String generateImagefileName()
	{
	    Calendar cal = Calendar.getInstance();
	    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH.mm.ss");
	    return sdf.format(cal.getTime());
	}
	
	public Boolean openConnection(String address,int port)
	{
		Log.d("DEBUG","openConnection");
		// Clear all socket
		closeConnection();
		try 
		{
			this.socket = new Socket(address,port);
			 // Again, probably better to store these objects references in the support class
		    in  = socket.getInputStream();
		    out = socket.getOutputStream();
		    this.runProcess = true;
		    return true;
		}
		catch (UnknownHostException e) 
		{
			Log.d("DEBUG","openConnection unknowHostException : "+e.getMessage());
			// Close connection and back to home
			finish();
		}
		catch (IOException e) 
		{
			Log.d("DEBUG","openConnection IOException : "+e.getMessage());
			// Close connection and back to home
			finish();
		}
		return false;
	}
	
	private void finish()
	{
		Log.d("DEBUG","finish");
		this.syncAct.progressDialog.dismiss();
		closeConnection();
		//this.syncAct.onBackPressed();
		this.syncAct.finish();
	}
	
	public void closeConnection()
	{
		runProcess = false;
		if(this.isAlive())
		{
			try
			{
				//this.stop();
			} catch (Exception e){
			}
		}
		if(socket != null)
		{
			try {
				socket.close();
			} catch (IOException e) {
			}
			socket = null;
		}
		if(in != null)
		{
			try {
				in.close();
			} catch (IOException e) {
			}
			in = null;
		}
		if(out != null)
		{
			try {
				out.close();
			} catch (IOException e) {
			}
			out = null;
		}
	}
	
	/**
	 * Start Sync String connection to Server
	 */
	public void startSyncRequest()
	{
		Log.d("DEBUG","startSyncRequest");
		
		int type = this.syncAct.extras.getInt("SYNC_TYPE");
		String[] datas;
		byte[] rawDatas;
		switch (type) {
			case VtAndroidSyncActivity.SYNC_TYPE_DOWNLOAD:
				Log.d("DEBUG","startSyncRequest: SYNC_TYPE_DOWNLOAD");
				// Send request data to server
				sendStringMsg(generateMsgRequestItem(this.syncAct.extras.getInt("ITEM_ID")));
				break;
			case VtAndroidSyncActivity.SYNC_TYPE_UPLOAD_PHOTO:
				Log.d("DEBUG","startSyncRequest: SYNC_TYPE_UPLOAD_PHOTO");
				// Start Upload data to server
				datas = (String[]) this.syncAct.extras.get("DATA");
				for (String imgPath : datas) 
				{
					sendPhoto(imgPath);
				}
				// dismiss the progress dialog
				this.syncAct.progressDialog.dismiss();
				// Close connection and back to home
				finish();
				break;
			case VtAndroidSyncActivity.SYNC_TYPE_UPLOAD_LINK:
				Log.d("DEBUG","startSyncRequest: SYNC_TYPE_UPLOAD_LINK "+type);
				// Start Upload link to server
				String url = (String) this.syncAct.extras.get("DATA");
				sendLink(url);
				// dismiss the progress dialog
				this.syncAct.progressDialog.dismiss();
				// Close connection and back to home
				finish();
				break;
			case VtAndroidSyncActivity.SYNC_TYPE_UPLOAD_CONTACT:
				Log.d("DEBUG","startSyncRequest: SYNC_TYPE_UPLOAD_CONTACT "+type);
				// Start Upldate contact to server
				rawDatas = (byte[]) this.syncAct.extras.get("DATA");
				sendContact(rawDatas);
				// Close connection and back to home
				finish();
				break;
			default :
				// Close connection and back to home
				finish();
				break;
		}
	}
	
	//////////////////////////////////////////////////////////////////////
	//
	// Message Sender
	//
	//////////////////////////////////////////////////////////////////////	
	private void sendStringMsg(String msg)
	{
		Log.d("DEBUG","sendStringMsg : msg = "+msg);
		try
		{
			out.write(msg.getBytes());
			out.flush();	// Flush message header
		} 
		catch (IOException e) 
		{
			Log.d("Network",e.getMessage());
		}
	}
	
	private String generateMsgRequestItem(int itemId)
	{
		String msg = "<requestitem id=\""+itemId+"\"/>";
		return msg;
	}
	
	private String generateMsgEnd()
	{
		String msg = "<end />";
		return msg;
	}
	
	/**
	 * Generate message header xml string
	 * @param msgType
	 * @param datasize
	 * @param optionals
	 * @return
	 */
	private String generateMsgHeader(String msgType,HashMap<String,String> optionals)
	{
		String msg = "<"+msgType+" ";
		if(optionals != null)
		{
			for(String key : optionals.keySet())
			{
				msg += key+"=\""+optionals.get(key)+"\" ";
			}
		}
		msg += "/>";
		return msg;
	}
	
	// Send image message byte array
	public void sendPhoto(String imagePath)
    {
		Log.d("DEBUG","sendPhoto");
		try 
		{
			FileInputStream imageFile = new FileInputStream( imagePath );
			String fileType = "jpg";
		    if(imagePath.endsWith("jpg") || imagePath.endsWith("JPG"))
		    {
			    fileType = "jpg";
		    }
		    else if(imagePath.endsWith("jpeg") || imagePath.endsWith("JPEG"))
		    {
			    fileType = "jpeg";
		    }
		    else if(imagePath.endsWith("png") || imagePath.endsWith("PNG"))
		    {
			    fileType = "png";
		    }
		    HashMap<String,String> optionals = new HashMap<String, String>();
		    optionals.put("filetype",fileType);
		    
		    ByteArrayOutputStream bos = new ByteArrayOutputStream();
		    byte[] buf = new byte[10240];
	        try
	        {
	            for (int readNum; (readNum = imageFile.read(buf)) != -1;) 
	            {
	                bos.write(buf, 0, readNum); //no doubt here is 0
	            }
	        } 
	        catch (IOException ex) 
	        {
	        }
	        byte[] databytes = bos.toByteArray();
	        optionals.put("datasize",""+databytes.length);
	        String msgHeader = generateMsgHeader(ConnectionManager.MSGTYPE_PHOTO,optionals);
	        Log.d("DEBUG","sendPhoto : msgHeader = "+msgHeader);
	        sendStringMsg(msgHeader);
		    out.write(databytes);
		    out.flush();
		}
		catch (FileNotFoundException e) 
		{
			// Close connection and back to home
			finish();
		} 
		catch (IOException e) 
		{
			// Close connection and back to home
			finish();
		}
    }
	
	public void sendLink(String url)
    {
		HashMap<String,String> optionals = new HashMap<String, String>();
	    optionals.put("url",url);
		String msgHeader = generateMsgHeader(ConnectionManager.MSGTYPE_LINK,optionals);
		sendStringMsg(msgHeader);
    }
	
	private void sendContact(byte[] vCardData)
	{
		try 
		{
			// Send Header
			HashMap<String,String> optionals = new HashMap<String, String>();
			optionals.put("datasize",""+vCardData.length);
			String msgHeader = generateMsgHeader(ConnectionManager.MSGTYPE_CONTACT,optionals);
			sendStringMsg(msgHeader);
			
			// Send data msg
			out.write(vCardData);
			out.flush();
		} catch (IOException e) {
			// Close connection and back to home
			finish();
		}
	    
	}
	
	//////////////////////////////////////////////////////////////////////
	//
	// Message Parser
	//
	//////////////////////////////////////////////////////////////////////
	
	// Get Header from input stream
	private String getHeaderMsg(InputStream in)
	{
		Log.d("DEBUG","getHeaderMsg");
		try 
	    {
			String headerMsg = "";
			int data = 0;
			
			while(data != -1)
			{
				try {Thread.sleep(80);} catch (InterruptedException e) {}
				while(in.available() > 0)
				{
					byte[] bytes = new byte[1];
					data = in.read(bytes);
					
		        	headerMsg += new String(bytes);
		        	
		        	// PHOTO MSG
		        	Matcher matcher = ConnectionManager.MSG_PHOTO_PATTERN.matcher(headerMsg);
		        	if(matcher.find())
					{
		        		Log.d("DEBUG","getHeaderMsg : MSG_PHOTO_PATTERN = "+ matcher.group());
		        		return  matcher.group();
					}
		        	matcher = ConnectionManager.MSG_CONTACT_PATTERN.matcher(headerMsg);
		        	if(matcher.find())
					{
		        		Log.d("DEBUG","getHeaderMsg : MSG_CONTACT_PATTERN = "+ matcher.group());
		        		return  matcher.group();
					}
		        	// ENG MSG
		        	matcher = ConnectionManager.MSG_END_PATTERN.matcher(headerMsg);
		        	if(matcher.find())
					{
		        		Log.d("DEBUG","getHeaderMsg : MSG_END_PATTERN = "+ matcher.group());
		        		return matcher.group();
					}
				}
	        }
	        if(data == -1)
	        {
	        	Log.d("DEBUG","getHeaderMsg data : -1");
	        	// Close connection and back to home
				finish();
	        }
	    }
		catch(IOException e)
	    {
			Log.d("DEBUG","getHeaderMsg IOException : "+e.getMessage());
			e.printStackTrace();
			// Close connection and back to home
			finish();
	    }
		return null;
	}
	
	// Get Data from input stream
	private byte[] getData(InputStream in,int dataSize) 
	{
		Log.d("DEBUG","getData");
		try 
	    {
	        ByteArrayOutputStream out = new ByteArrayOutputStream();
	        int data = 0;
	        while(data != -1)
			{
	        	try {Thread.sleep(80);} catch (InterruptedException e) {}
				while(in.available() > 0 && data != -1)
				{
					byte[] bytes;
					if(dataSize - out.size()  <= in.available())
					{
						bytes = new byte[(dataSize - out.size())];
					}
					else
					{
						bytes = new byte[in.available()];
					}
					
					data = in.read(bytes);
					out.write(bytes);
					if(out.size() == dataSize)
					{
						 return out.toByteArray();
					}
				}
			}
	    }
	    catch(IOException e)
	    {
	    	Log.d("DEBUG","getData IOException : "+e.getMessage());
	    	// Close connection and back to home
	    	finish();
	    }
	    return null;
	}
	
	//////////////////////////////////////////////////////////////////////
	public void run()
	{
		startSyncRequest();
		while(runProcess)
		{
			// Get XML Header
			String msg = getHeaderMsg(in);
			if(msg == null)
			{
				this.runProcess = false;
				return;
			}
			
			// Convert xml to string
			org.w3c.dom.Element msgDom = null;
			try 
			{
				msgDom = (org.w3c.dom.Element) DocumentBuilderFactory
					    .newInstance()
					    .newDocumentBuilder()
					    .parse(new ByteArrayInputStream(msg.getBytes()))
					    .getDocumentElement();
			}
			catch (Exception e)
			{
				Log.d("DEBUG","run : Exception : "+e.getMessage());
				finish();
				return;
			}
			
			//2. Activate type of xml command
			if(msgDom.getTagName().equalsIgnoreCase(ConnectionManager.MSGTYPE_END))
			{
				Log.d("DEBUG","run : MSGTYPE_END FINISH");
				// Close connection and back to home
				finish();
				return;
			}
			else if(msgDom.getTagName().equalsIgnoreCase(ConnectionManager.MSGTYPE_CONTACT))
			{
				Log.d("DEBUG","run : MSGTYPE_CONTACT");
				int dataSize = Integer.parseInt(msgDom.getAttribute("datasize"));
				// Get data
				byte[] byeDataArray = getData(in,dataSize);
				if(byeDataArray == null)
				{
					this.runProcess = false;
					return;
				}
				
				// Write vCard into temp file
				final String filename = "vCard.vcf";
				File fileDirectory = new File("/sdcard/vSync/");
				fileDirectory.mkdirs();
				File file = new File("/sdcard/vSync/",filename);
				if (file.exists())
				{
					file.delete();
				}
				try 
				{
					FileOutputStream fos = new FileOutputStream(file.getPath());
		        	fos.write(byeDataArray);
		        	fos.close();
				}
				catch (java.io.IOException e) 
				{
					Log.d("DEBUG","run MSGTYPE_CONTACT : IOException "+e.getMessage());
					finish();
					return;
				}
				
				// Close connection and back to home
				this.finish();
				
				Intent saveContact = new Intent();
				saveContact.setAction(android.content.Intent.ACTION_VIEW);
				saveContact.setDataAndType(Uri.parse("file:///mnt/sdcard/vSync/vCard.vcf"), "text/x-vcard");
				this.syncAct.startActivity(saveContact);
				return;
			}
			else if(msgDom.getTagName().equalsIgnoreCase(ConnectionManager.MSGTYPE_PHOTO))
			{
				Log.d("DEBUG","run : MSGTYPE_PHOTO");
				String type = msgDom.getAttribute("filetype");
				int dataSize = Integer.parseInt(msgDom.getAttribute("datasize"));
				
				// Get data
				byte[] byeDataArray = getData(in,dataSize);
				if(byeDataArray == null)
				{
					this.runProcess = false;
					return;
				}
				
				// Save file to local storage
				File fileDirectory = new File("/sdcard/vSync/");
				fileDirectory.mkdirs();
				final String filename = generateImagefileName()+"."+type;
				File file = new File("/sdcard/vSync/",filename);
				if (file.exists()) 
				{
					file.delete();
				}
				try 
				{
					FileOutputStream fos = new FileOutputStream(file.getPath());
		        	fos.write(byeDataArray);
		        	fos.close();
				}
				catch (java.io.IOException e) 
				{
					Log.e("PictureDemo", "Exception in photoCallback", e);
				}
				
				// Popup message for notice user already get new photo
				this.syncAct.runOnUiThread(new Runnable() 
				{
					public void run() 
					{
						// Try to Update Gallery
						Toast toast = Toast.makeText(syncAct.getApplicationContext(),"Receive new Photo",Toast.LENGTH_LONG);
						toast.show();
					}
				});
			}
		}
	}
	//////////////////////////////////////////////////////////	
}
